# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import os
import datetime
import logging
import pandas
import pprint
import sys
import tabulate

from tornado.ioloop import IOLoop

import coin.flow.subscriber as flow_subscriber
from coin.base.param_util import to_list
from coin.flow.simple_queue import SimpleQueue
from coin.strategy.mm.base.multi_feed_subsystem import MultiFeedSubsystem
from coin.strategy.mm.live_envs import get_live_env_cls

if ('COIN2_FEED_SUBSYSTEM' in os.environ):
  raise RuntimeError("deprecated")


def _print_working_orders(wo):
  orders = []
  for order in wo:
    internal_order_id = None
    try:
      internal_order_id = order.internal.get("bitflyer_order_id", None)
    except Exception:
      pass
    orders.append({
        'id': order.order_id,
        'cancel_sent': order.cancel_sent,
        'side': order.order_side,
        'price': order.price,
        'qty': order.qty,
        'accepted': order.accepted,
        'order_id': internal_order_id,
        'time': pandas.DatetimeIndex([order.internal.timestamp])[0]
    })
  df = pandas.DataFrame(orders)
  if len(df.index) > 0:
    pandas.set_option('display.width', 1000)
    print(df)


class UnaryMultifeedStrategyBase(object):
  CurrencyType = None
  ProductType = None
  LiveEnvironmentType = None
  OGName = "Unknown"

  def __init__(self, products, currencies, print_balance=False, logger=None):
    self._logger = logger or logging.getLogger('UnaryMultifeedStrategyBase')

    products = to_list(products)
    # assert all([type(product) == self.ProductType for product in products]), products
    self._products = products

    currencies = to_list(currencies)
    # assert all([type(currency) == self.CurrencyType for currency in currencies]), currencies
    self._currencies = sorted(currencies, key=lambda currency: currency.currency)
    self._print_balance = print_balance
    self._balance_print_internval = datetime.timedelta(seconds=30)
    self._skip_balance = False
    self._skip_position = False

    self._ioloop = None
    self._queue = None
    self._flow_sub = None

    self._feed_subsystem = None
    self._og = None

  def is_og_ready(self):
    return (self._og is not None and self._og.is_ready())

  @property
  def order_gateway(self):
    return self._og

  @property
  def feed_subsystem(self):
    return self._feed_subsystem

  def register_flow(self, ioloop, queue, flow_sub):
    self._ioloop = ioloop
    self._queue = queue
    self._flow_sub = flow_sub

  def register_feed_subsystem(self, feed_subsystem):
    self._feed_subsystem = feed_subsystem

  def register_order_gateway(self, name, order_gateway):
    self._logger.info('OG initialized: %s', name)
    if name == self.OGName:
      self._og = order_gateway
    else:
      raise ValueError('Unknown OG: %s' % name)

    if self._print_balance:
      self._start_print_balance()
    self._ioloop.add_callback(self.on_og_reset)

  def prepare(self):
    return

  def on_og_reset(self):
    raise NotImplementedError()

  def _get_pos(self, product):
    pos = self._og.get_position(product)

    # TODO(jhkim): implement this
    # book = self._bookmap.get_book(product)
    # if book is None or not book.has_ask() or not book.has_bid():
    #  return None, None
    # avg_entry_price = self._og.get_average_entry_price(product)
    # unrealized_pnl = calculate_unrealized_pnl(
    #    pos, avg_entry_price, book.ask0().price, book.bid0().price)
    return pos, None

  def _print_balance_impl(self):
    if self._og is None or not self._og.is_ready():
      return

    table = []
    for currency in self._currencies:
      total = 0.
      for product in self._products:
        if product.base == currency:
          pos, unrealized_pnl = self._get_pos(product)
          total += (unrealized_pnl or 0.)
          if hasattr(self._og, "get_okex_futures_position"):
            okex_position = self._og.get_okex_futures_position(product)
            buyopen = okex_position.long
            sellopen = okex_position.short
          elif hasattr(self._og, "get_huobi_futures_position"):
            huobi_position = self._og.get_huobi_futures_position(product)
            buyopen = huobi_position.long
            sellopen = huobi_position.short
          else:
            buyopen = max(0, pos)
            sellopen = min(0, pos)
          table.append(['*', str(product), pos, buyopen, sellopen, unrealized_pnl])

      balance = self._og.get_balance(currency.currency)
      total += balance

      table.append(['*', currency.currency, None, None, None, balance])
      table.append(['*', 'Total', None, None, None, total])

    pos_bal_table_str = tabulate.tabulate(
        table, headers=['*', 'Product', 'Position', 'Pos+', 'Pos-', 'Value'], floatfmt='.3f')
    self._logger.info('----- Position and balance:\n* %s\n%s',
                      str(datetime.datetime.now()),
                      pos_bal_table_str)

    if self.is_og_ready():
      if not self._skip_position:
        self._logger.info('\n%s', pandas.DataFrame([self._og.get_positions()], index=['pos']))
      if not self._skip_balance:
        self._logger.info('\n%s', pandas.DataFrame([self._og.get_balances()], index=['bal']))
      _print_working_orders(self._og.get_working_order())
    else:
      pprint.pprint("order gateway is not ready")
    sys.stdout.flush()

  def _start_print_balance(self):
    try:
      self._print_balance_impl()
    finally:
      self._ioloop.add_timeout(self._balance_print_internval, self._start_print_balance)

  @classmethod
  def _set_traits(cls, trade_subreq):
    order_cls = get_live_env_cls(trade_subreq)
    cls.CurrencyType = order_cls.OrderGateway.OGInfoType.CurrencyType
    cls.ProductType = order_cls.OrderGateway.OGInfoType.ProductType
    cls.LiveEnvironmentType = order_cls
    cls.OGName = order_cls.OrderSubReq
    assert cls.CurrencyType is not None
    assert cls.ProductType is not None

  @classmethod
  def run_live(
      cls,
      *,
      strategy,
      og_config_name,
      on_order_gateway_reset_callback,
      init_order_gateway_after=None,
      exit_after=None,
      fs_config=None,
      og_params=None,
      feed_checker_config=None,
      og_log_callback=None,
      slow_callback_duration=None,
      og_config_dict=None,
  ):
    # Initialize loop
    ioloop = IOLoop.current()
    if slow_callback_duration is not None:
      assert slow_callback_duration > 0
      ioloop.asyncio_loop.slow_callback_duration = slow_callback_duration
    queue = SimpleQueue('queue')
    flow_sub = flow_subscriber.from_queue(queue)
    strategy.register_flow(ioloop, queue, flow_sub)

    # Initialize feed
    feed_subsystem = MultiFeedSubsystem(feed_checker_config=feed_checker_config,
                                        fs_config=fs_config)
    strategy.register_feed_subsystem(feed_subsystem)

    # Initialize OG live environment
    env = cls.LiveEnvironmentType(currencies=strategy._currencies,
                                  trade_products=strategy._products,
                                  og_config_name=og_config_name,
                                  og_params=og_params,
                                  init_og_after=init_order_gateway_after,
                                  ioloop=ioloop,
                                  queue=queue,
                                  flow_sub=flow_sub,
                                  og_log_callback=og_log_callback,
                                  og_config_dict=og_config_dict)
    env.on_order_gateway_reset_callback.append(strategy.register_order_gateway)

    # Start
    strategy.prepare()
    feed_subsystem.start(ioloop, queue, flow_sub)
    env.start()

    if exit_after is not None:
      if hasattr(feed_subsystem, "stop"):
        print("Register feed subsystem stop!(good msg don't panic)")
        ioloop.add_timeout(exit_after, feed_subsystem.stop)
      ioloop.add_timeout(exit_after, ioloop.stop)

    try:
      ioloop.start()
      return 0
    except KeyboardInterrupt:
      return 1
